﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace SimpleDatabaseAdapter
{
    public class Lists : ILists
    {
        #region Constructor
        private SimpleDataAdapter _Parent;
        internal Lists(SimpleDataAdapter parent, bool checkTable = false)
        {
            _Parent = parent;
            if (checkTable) CheckTable();
        }
        #endregion
        
        #region Cache
        private object _lock = new object();
        private DataTable dtCache;
        private DateTime cahceDate = DateTime.MinValue;
        private readonly TimeSpan cacheTimeSpan = new TimeSpan(0, 5, 0);
        #endregion

        #region SQL strings
        private string ListsTableName
        {
            get
            {
                return _Parent.Config.TableListsName;
            }
        }
        private string GetSQLQueryGetAllLists()
        {
            string query = string.Format(SQLPatterns.SQL_QUERY_GET_ALL_LISTS_PATTERN, ListsTableName);
            return query;
        }
        private string GetSQLQueryPatternAddList()
        {
            string query = string.Format(SQLPatterns.SQL_QUERY_ADD_LIST_PATTERN, ListsTableName, ListColumns.COLUMN_TITLE, ListColumns.COLUMN_STATIC_NAME, ListColumns.COLUMN_COLUMNS);
            return query;
        }
        private string GetSQLQueryPatternUpdateList()
        {
            string query = string.Format(SQLPatterns.SQL_QUERY_UPDATE_LIST_PATTERN, ListsTableName, ListColumns.COLUMN_ID, ListColumns.COLUMN_TITLE, ListColumns.COLUMN_STATIC_NAME, ListColumns.COLUMN_COLUMNS);
            return query;
        }
        #endregion

        #region Public functions
        public void AddList(ListData listData)
        {
            AddLists(new ListData[] { listData });
        }
        public void AddLists(ListData[] newLists)
        {
            string queryAddLists = String.Empty; ;

            foreach (ListData listData in newLists)
            {
                if (!IsListExists(listData.StaticName))
                {
                    string queryAaddListPattern = GetSQLQueryPatternAddList();
                    queryAddLists += string.Format(queryAaddListPattern, listData.Title, listData.StaticName, listData.Columns);
                    
                }
                else
                {
                    throw new Exception(string.Format("Список '{0}' уже существует.", listData.StaticName));
                }
            }

            _Parent.DatabaseAccess.ExecuteQuery(queryAddLists);

            ResetAllListsCache();
        }
        public void UpdateList(ListData list)
        {
            UpdateLists(new ListData[] { list });
        }
        public void UpdateLists(ListData[] lists)
        {
            string queryUpdateListPattern = GetSQLQueryPatternUpdateList();
            StringBuilder queryUpdateLists = new StringBuilder();

            foreach (ListData list in lists)
            {
                queryUpdateLists.AppendLine(string.Format(queryUpdateListPattern, list.Id, list.Title, list.StaticName, list.Columns));
            }

            _Parent.DatabaseAccess.ExecuteQuery(queryUpdateLists.ToString());

            ResetAllListsCache();
        }
        public void DeleteList(int id)
        {
            DeleteLists(new List<int>() { id });
        }
        public void DeleteLists(List<int> ids)
        {
            string query = String.Empty;

            foreach (int id in ids)
            {
                query += string.Format(SQLPatterns.SQL_QUERY_DELETE_LIST_PATTERN, _Parent.Config.TableListsName, ListColumns.COLUMN_ID, id);
            }

            _Parent.DatabaseAccess.ExecuteQuery(query);
        }

        public ListDataCollection GetAllLists()
        {
            DataTable dt = GetListsData();
            ListDataCollection lists = new ListDataCollection(dt, this);
            return lists;
        }
        public ListData GetList(string listName)
        {
            ListData listData;

            listData = GetListByStaticName(listName);
            if (listData == null)
            {
                listData = GetListByTitle(listName);
            }

            return listData;
        }
        public ListData GetList<T>()
        {
            Type listItemsType = typeof(T);
            ListName attr = GetListaNameAttribute<T>();

            if (_Parent.Lists.IsListExists(attr.StaticName))
            {
                ListData list = _Parent.Lists[attr.StaticName];
                return list;
            }

            return null;
        }
        public ListData CheckAndGetList<T>()
        {
            Type listItemsType = typeof(T);
            if (CheckList<T>())
            {
                ListName attr = GetListaNameAttribute<T>();
                ListData list = _Parent.Lists[attr.StaticName];
                return list;
            }

            return null;
        }
        public bool CheckList<T>()
        {
            Type listItemsType = typeof(T);
            ListName attr = GetListaNameAttribute<T>();

            if (!_Parent.Lists.IsListExists(attr.StaticName))
            {
                ListData listData = new ListData()
                {
                    StaticName = attr.StaticName,
                    Title = attr.Title
                };

                _Parent.Lists.AddList(listData);

               
            }

            ListData newList = _Parent.Lists[attr.StaticName];
            CheckColumnsInList(newList, listItemsType);

            return true;
        }
        public ListData GetListByStaticName(string listName)
        {
            DataRow row = SelectFirstRow(string.Format("{0} = '{1}'", ListColumns.COLUMN_STATIC_NAME, listName));
            if (row != null)
            {
                ListData list = new ListData(row, this);
                return list;
            }
            return null;
        }
        public ListData GetListByTitle(string listName)
        {
            DataRow row = SelectFirstRow(string.Format("{0} = '{1}'", ListColumns.COLUMN_TITLE, listName));
            if (row != null)
            {
                ListData list = new ListData(row, this);
                return list;
            }
            return null;
        }
        public ListData GetListById(int id)
        {
            DataRow row = SelectFirstRow(string.Format("{0} = {1}", ListColumns.COLUMN_ID, id));
            if (row != null)
            {
                ListData list = new ListData(row, this);
                return list;
            }
            return null;
        }
        public bool IsListExists(string listName)
        {
            ListData list = GetList(listName);
            return list != null;
        }
        public void CheckTable()
        {
            _Parent.TableCreator.CheckListsTable();
        }

        public ListData this[int index]
        {
            get
            {
                DataTable dt = GetListsData();
                ListData listData = new ListData(dt.Rows[index], this);
                return listData;
            }
        }
        public ListData this[string listName] 
        {
            get
            {
                ListData listData = GetList(listName);
                return listData;
            }
        }
        #endregion

        #region Lists data
        private DataTable GetListsData()
        {
            DataTable dt;
            if (!TryGetAllListsFromCache(out dt))
            {
                dt = GetAllListsFromDataBase();
                SetAllListsCache(dt);
            }
            return dt;
        }
        
        private bool TryGetAllListsFromCache(out DataTable dt)
        {
            if (IsAllListsCache())
            {
                lock (_lock)
                {
                    if (IsAllListsCache())
                    {
                        dt = GetAllListsFromCache();
                        return true;
                    }
                }
            }

            dt = null;
            return false;
        }
        private bool IsAllListsCache()
        {
            if (dtCache != null)
            {
                if (DateTime.Now - cahceDate < cacheTimeSpan)
                {
                    //If the time has NOT expired cache
                    return true;
                }
                else
                {
                    //If the time has expired cache
                    ResetAllListsCache(); //Reset cahce
                    return false;
                }
            }

            return false;
        }       
        private DataTable GetAllListsFromCache()
        {
            return dtCache;
        }
        private void SetAllListsCache(DataTable dt)
        {
            dtCache = dt;
            cahceDate = DateTime.Now;
        }
        public void ResetAllListsCache()
        {
            SetAllListsCache(null);
        }

        private DataTable GetAllListsFromDataBase()
        {
            string queryGetAllLists = GetSQLQueryGetAllLists();
            DataTable dt = _Parent.DatabaseAccess.GetData(queryGetAllLists);
            return dt;
        }

        private DataRow SelectFirstRow(string filter)
        {
            DataRow[] rows = SelectRows(filter);
            if (rows.Length > 0)
            {
                return rows[0];
            }
            return null;
        }
        private DataRow[] SelectRows(string filter)
        {
            DataTable dt = GetListsData();
            DataRow[] rows = dt.Select(filter);
            return rows;
        }

        private ListName GetListaNameAttribute<T>()
        {
            Type listItemsType = typeof(T);
            ListName attr = (ListName)Attribute.GetCustomAttribute(listItemsType, typeof(ListName));

            if (attr == null)
            {
                throw new Exception(string.Format("'{0}' not class data.", listItemsType));
            }
            return attr;
        }
        private void CheckColumnsInList(ListData list, Type listItemsType)
        {
            List<ListColumnBase> columns = list.GetColumnsData();
            List<string> noUseColumnsNames = columns.Select(x => x.StaticName).ToList();

            PropertyInfo[] propertyes = Propertyes.GetAllDataItemPropertyesNoBase(listItemsType);
            Type columnAttribute = typeof(AllDataItemAttribute);

            //Check new propertyes
            bool isChange = false;
            foreach (PropertyInfo property in propertyes)
            {
                AllDataItemAttribute attr = property.GetCustomAttribute(columnAttribute) as AllDataItemAttribute;

                if (AllDataColumns.IsSystemColumn(property.Name))
                {
                    throw new Exception(string.Format("Class property '{0}' is system.", property.Name));
                }
                if (AllDataColumns.IsSystemColumn(attr.StaticName))
                {
                    throw new Exception(string.Format("Static column name '{0}' is system.", attr.StaticName));
                }

                if (string.IsNullOrEmpty(attr.StaticName))
                {
                    throw new Exception("Static name is empty");
                }
                if (string.IsNullOrEmpty(attr.Title))
                {
                    throw new Exception("Title is empty");
                }
               
                if (columns.Where(x => x.StaticName == attr.StaticName).Count() == 0)
                {
                    list.AddColumn(new ListColumnBase()
                    {
                        StaticName = attr.StaticName,
                        Title = attr.Title,
                        Type = attr.Type
                    });

                    isChange = true;
                }
                else
                {
                    noUseColumnsNames.Remove(noUseColumnsNames.Where(x => x == attr.StaticName).First());
                }
            }

            //Delete no use propertyes
            foreach (string column in noUseColumnsNames)
            {
                list.DeleteColumn(column);
                isChange = true;
            }

            if (isChange) list.UpdateList();
        }
        #endregion
    }
}
